﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;

    internal partial class SendReceiveHelper<TConnection> where TConnection : Connection
    {
        private abstract class FileOperationContext : OperationContext
        {
            private readonly byte[] buffer = new byte[4096];
            private string previousPath;
            private FileStream fileStream;

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            internal FileOperationContext(SendReceiveContext context, IProgress<ISendReceiveProgress> progress) :
                base(context, progress)
            {
                this.Root.FileIOProvider.CreateDirectory(this.Group.DataDirectory);
            }

            internal virtual void SaveContent(IInboundArticleInfo article, Stream content)
            {
                var path = InboundArticle.GetDataFilePath(
                    this.Group.DataDirectory, this.Group.CachedInboundArticlesLimit, article.Number);

                if (this.previousPath != path)
                {
                    if (this.fileStream != null)
                    {
                        this.fileStream.Dispose();
                    }

                    Func<FileStream> open =
                        () => this.Root.FileIOProvider.OpenFile(path, FileMode.Append, FileAccess.Write, FileShare.Read);
                    this.fileStream = WrapFileIOException(open, path);
                    this.previousPath = path;
                }

                WrapFileIOException(() => SaveContent(article, content, this.fileStream, this.buffer), path);
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Dispose() must never throw.")]
            protected sealed override void Dispose(bool disposing)
            {
                try
                {
                    if (disposing && (this.fileStream != null))
                    {
                        this.fileStream.Dispose();
                    }
                }
                catch
                {
                }
                finally
                {
                    base.Dispose(disposing);
                }
            }

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////

            private static void SaveContent(
                IInboundArticleInfo article, Stream content, FileStream fileStream, byte[] buffer)
            {
                var offset = fileStream.Position;
                int read;

                while ((read = WrapNetworkIOException(() => content.Read(buffer, 0, buffer.Length))) > 0)
                {
                    fileStream.Write(buffer, 0, read);
                }

                ((InboundArticle)article).SetArticle(offset, fileStream.Position - offset);
            }

            private static void WrapFileIOException(Action action, string message)
            {
                WrapFileIOException(() => { action(); return false; }, message);
            }

            private static T WrapFileIOException<T>(Func<T> func, string message)
            {
                try
                {
                    return func();
                }
                catch (ConnectionLostException)
                {
                    throw;
                }
                catch (IOException ex)
                {
                    // Apparently, it is not possible to get a more detailed cause for the IOException, not even by
                    // calling Marshal.GetHRForException. We therefore can't do anything better than report that
                    // storage failed.
                    throw new WriteFailedException(message, ex);
                }
            }

            private static T WrapNetworkIOException<T>(Func<T> func)
            {
                try
                {
                    return func();
                }
                catch (IOException ex)
                {
                    throw new ConnectionLostException("Connection has been lost.", ex);
                }
            }
        }
    }
}
